using System;
using System.Collections.Generic;
using System.Text;

using Danser_Entity.Queries.Data;
using Danser_Entity.Utils;

namespace Danser_Entity.Queries.Filters
{
    /// <summary>
    /// Represents a rule for validation or filtering the Test tables
    /// </summary>
    public class RuleExpression
    {
        #region Members:

        private IList<Condition> _conditions;

        #endregion

        #region C'tors:

        /// <summary>
        /// creates an empty RuleExpression instance
        /// </summary>
        public RuleExpression()
        {
            _conditions = new List<Condition>();
        }

        /// <summary>
        /// creates a RuleExpression instance
        /// </summary>
        /// <param name="conditions">
        /// a list of Conditions creating the rule
        /// </param>
        public RuleExpression(IList<Condition> conditions)
        {
            _conditions = conditions;
        }

        #endregion

        #region Properties:

        /// <summary>
        /// The conditions list building the rule
        /// </summary>
        public IList<Condition> Conditions
        {
            get { return _conditions; }
        }

        #endregion

        #region Methods:

        /// <summary>
        /// Displays a string representation of the rule composed out of conditions ids only 
        /// </summary>
        /// <returns></returns>
        public String ConditionIdsToString()
        {
            StringBuilder ruleString = new StringBuilder();
            if (_conditions[0].Not)
            {
                 ruleString.Append("NOT");
            }
            ruleString.Append("(0)");

            for (int i = 1; i < _conditions.Count; i++)
            {
                ruleString.Append(" " + _conditions[i].BoolOperator);
                
                if (_conditions[i].Not)
                    ruleString.Append(" NOT");

                ruleString.Append(" (" + i + ")");
            }
            return ruleString.ToString() ;
        }

        /// <summary>
        /// Displays a string representation of the rule composed out of conditions 
        /// </summary>
        /// <param name="SQLFormat">create an SQL formated string if set to true</param>
        /// <returns></returns>
        public String ConditionsToString(bool SQLFormat)
        {
            StringBuilder ruleString = 
                new StringBuilder(serializeCondition(_conditions[0], SQLFormat));

            for (int i = 1; i < _conditions.Count; i++)
            {
                ruleString.Append(" " + _conditions[i].BoolOperator);
                
                ruleString.Append(serializeCondition(_conditions[i], SQLFormat));
            }

            return ruleString.ToString();


        }

        #endregion

        #region Static Methods:

        /// <summary>
        /// Convert a string to Rule containing the list of conditions as represented by the string
        /// </summary>
        /// <param name="ruleToParse">The string to convert</param>
        /// <returns></returns>
        public static RuleExpression ParseRuleFromString(String ruleToParse)
        {
            if (String.IsNullOrEmpty(ruleToParse))
                return null;

            //split by the brackets
            //will create an array of boolean operator and next - the expression
            String[] conditionStrings = ruleToParse.Split(
                new String[] { "(", ")" },
                StringSplitOptions.RemoveEmptyEntries);

            IList<Condition> conditions = new List<Condition>();
            //parse first condition
            Condition parsedCondition = new Condition();

            String firstElement = conditionStrings[0].Trim();
            String comparingExpression = comparingExpression = firstElement;
            int startIndex = 1;

            //check if "not" was selected for 1st expression
            if (isNot(firstElement))
            {
                parsedCondition.Not = true;
                comparingExpression = conditionStrings[1].Trim();
                startIndex = 2;
            }
            //else
            parsedCondition.IsFirst = true;
            SetExpression(parsedCondition, comparingExpression);

            conditions.Add(parsedCondition);

            for (int i = startIndex; i < conditionStrings.Length; i += 2)
            {
                parsedCondition = new Condition();
                //1. parse boolean operator
                String boolOperator = conditionStrings[i].Trim();
                SetBooleanOperator(parsedCondition, boolOperator);

                //2. parse comparing expression
                comparingExpression = conditionStrings[i + 1].Trim();
                SetExpression(parsedCondition, comparingExpression);

                conditions.Add(parsedCondition);
            }

            return new RuleExpression(conditions);
        }

        #endregion

        #region Helpers:

        /// <summary>
        /// Create a String representation of an operator
        /// </summary>
        /// <param name="op">the operator to convert</param>
        /// <param name="SQLFormat">set to true if operatorthe SQL representation of the operator</param>
        /// <returns>The string representation of the condition</returns>
        private String serializeOperator(ComparingOperators op, bool SQLFormat)
        {
            String operatorString = null;
            switch (op)
            {
                case ComparingOperators.EQUALS:
                    operatorString = "=";
                    break;
                    
                case ComparingOperators.GREATER:
                    operatorString = ">";
                    break;

                case ComparingOperators.GREATER_EQUAL:
                    operatorString = ">=";
                    break;
                case ComparingOperators.NOT_EQUALS:
                    operatorString = "<>";
                    break;
                case ComparingOperators.LESS:
                    operatorString = "<";
                    break;
                case ComparingOperators.LESS_EQUAL:
                    operatorString = "<=";
                    break;
                //in case of special operator
                default:
                    operatorString = SQLFormat ? "LIKE" : op.ToString();
                    break;
            }
            return operatorString;
        }

        /// <summary>
        /// Create a String representation of a single Rule condition
        /// </summary>
        /// <param name="condition">the condition to convert</param>
        /// <param name="SQLFormat">set to true if string is wanted in an SQL format</param>
        /// <returns>The string representation of the condition</returns>
        private String serializeCondition(Condition condition, bool SQLFormat)
        {
            StringBuilder conditionString = new StringBuilder();
            String conditionValue = condition.Value;
            
            if (condition.Not)
                conditionString.Append(" NOT");

            conditionString.Append("(");

            if (SQLFormat)
            {
                if (Utils.Utils.IsFieldTypeString(condition.Field))
                {
                    conditionValue = FormatStringValue(condition.Operator, conditionValue);
                }
                else if (Utils.Utils.IsFieldTypBoolean(condition.Field))
                    conditionValue = "\'" + conditionValue + "\'";
            }
            else
            {
                conditionString.Append(condition.Group.ToString());
                conditionString.Append(".");
            }

            conditionString.Append(condition.Field + " ");
            conditionString.Append(serializeOperator(condition.Operator, SQLFormat) + " ");
            conditionString.Append(conditionValue);
            conditionString.Append(")");

            return conditionString.ToString();
        }

        /// <summary>
        /// Convert the group name to table name
        /// </summary>
        /// <param name="group"></param>
        /// <returns></returns>
        private String ExtractTableNameFromGroup(Groups group)
        {
            String result = null;

            switch (group)
            {
                case Groups.Information:
                    result = DBConstants.TEST_INFO_TABLE;
                    break;
                case Groups.Parameters:
                    result = DBConstants.TEST_PARAMS_TABLE; 
                    break;
                case Groups.Results:
                    result = DBConstants.TEST_RESULTS_TABLE;
                    break;
                case Groups.Users:
                    result = DBConstants.USERS_TABLE;
                    break;
                case Groups.ValidationTemplates:
                    result = DBConstants.VALIDATION_TEMPLATES_TABLE;
                    break;
            }
            return result;
        }

        /// <summary>
        /// Set the boolean operator related to the specified condition
        /// </summary>
        /// <param name="condition">The condition to update</param>
        /// <param name="boolOperator">The string to parse</param>
        private static void SetBooleanOperator(Condition condition, String boolOperator)
        {
            String[] boolOpDetails = boolOperator.Split(new char[] { ' ' });
            condition.BoolOperator = parseBooleanOperator(boolOpDetails[0]);
            condition.Not = (boolOpDetails.Length > 1);
        }

        /// <summary>
        /// Set the expression (field, operator, value) for the specified condition
        /// </summary>
        /// <param name="condition">The condition to update</param>
        /// <param name="comparingExpression">The string to parse</param>
        private static void SetExpression(Condition condition, String comparingExpression)
        {
            String[] expressionDetails = comparingExpression.Split(new char[] { ' ' });
            String[] fieldDetails = expressionDetails[0].Split(new char[] { '.' });
            condition.Group = parseGroup(fieldDetails[0]);
            condition.Field = fieldDetails[1];

            condition.Operator = parseOperator(expressionDetails[1]);
            //value can be empty if the field is a string, and the operator is sepcial string operator (CONTAINS etc.)
            if (expressionDetails.Length > 2)
                condition.Value = expressionDetails[2];
        }

        /// <summary>
        /// Convert from string to comparing operator
        /// </summary>
        /// <param name="op"></param>
        /// <returns></returns>
        private static ComparingOperators parseOperator(String op)
        {
            String normalizedOp = NormalizeString(op);
            
            ComparingOperators cOperator = ComparingOperators.EQUALS; //default
            switch (normalizedOp)
            {
                case "=":
                    cOperator = ComparingOperators.EQUALS;
                    break;
                case ">":
                    cOperator = ComparingOperators.GREATER;
                    break;
                case ">=":
                    cOperator = ComparingOperators.GREATER_EQUAL;
                    break;
                case "<>":
                    cOperator = ComparingOperators.NOT_EQUALS;
                    break;
                case "<":
                    cOperator = ComparingOperators.LESS;
                    break;
                case "<=":
                    cOperator = ComparingOperators.LESS_EQUAL;
                    break;
                case "CONTAINS":
                    cOperator = ComparingOperators.CONTAINS;
                    break;
                case "STARTS_WITH":
                    cOperator = ComparingOperators.STARTS_WITH;
                    break;
                case "ENDS_WITH":
                    cOperator = ComparingOperators.ENDS_WITH;
                    break;
            }

            return cOperator;
        }

        /// <summary>
        /// Convert string from html format characters to regular text
        /// </summary>
        /// <param name="htmlFormt">the string to convert</param>
        /// <returns></returns>
        private static String NormalizeString(String htmlFormt)
        {
            String normalizedOp1 = htmlFormt.Replace("&gt;", ">");
            String normalizedOp2 = normalizedOp1.Replace("&lt;", "<");

            return normalizedOp2;
        }

        /// <summary>
        /// Convert from string to Group enumeration element
        /// </summary>
        /// <param name="groupString">string representing the Group Element</param>
        /// <returns>Groups element</returns>
        private static Groups parseGroup(String groupString)
        {
            Groups group = Groups.Information; //default
            switch (groupString)
            {
                case "Information":
                    group = Groups.Information;
                    break;
                case "Parameters":
                    group = Groups.Parameters;
                    break;
                case "Results":
                    group = Groups.Results;
                    break;
                case DBConstants.USERS_TABLE:
                    group = Groups.Users;
                    break;
                case DBConstants.VALIDATION_TEMPLATES_TABLE:
                    group = Groups.ValidationTemplates;
                    break;
            }

            return group;
        }

        /// <summary>
        /// Check if operator "not" was selected for this condition
        /// </summary>
        /// <param name="boolOp"></param>
        /// <returns></returns>
        private static bool isNot(String boolOp)
        {
            return (boolOp == null ? false :
                (boolOp.Equals("NOT") ? true : false));
        }

        /// <summary>
        /// Convert from string to boolean operator
        /// </summary>
        /// <param name="boolOp"></param>
        /// <returns></returns>
        private static BooleanOperators parseBooleanOperator(String boolOp)
        {
            switch (boolOp)
            {
                case "AND":
                    return BooleanOperators.AND;

                case "OR":
                    return BooleanOperators.OR;
                default:
                    return BooleanOperators.AND;
            }
        }

        /// <summary>
        /// Format the value in the correct SQL syntax according to give operator
        /// </summary>
        /// <param name="stringOperator">the operator defining the format type</param>
        /// <param name="value">teh string value to edit</param>
        /// <returns>
        /// string reprenting the correct syntax
        /// </returns>
        private String FormatStringValue(ComparingOperators stringOperator, String value)
        {
            StringBuilder valueBuilder = new StringBuilder("\'");
            switch (stringOperator)
            {
                case ComparingOperators.CONTAINS:
                    valueBuilder.Append("%" + value + "%");
                    break;
                case ComparingOperators.STARTS_WITH:
                    valueBuilder.Append(value + "%");
                    break;
                case ComparingOperators.ENDS_WITH:
                    valueBuilder.Append("%" + value);
                    break;
                default:
                    valueBuilder.Append(value);
                    break;
            }
            valueBuilder.Append("\'");

            return valueBuilder.ToString();
        }

        #endregion
    }
}
